using EFCoreEnterpriseDemo.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace EFCoreEnterpriseDemo.Extensions
{
    public static class QueryableExtensions
    {
        public static IQueryable<T> SelectFields<T>(this IQueryable<T> query, List<string> includeFields) where T : class
        {
            if (includeFields == null || !includeFields.Any())
                return query;

            var parameter = Expression.Parameter(typeof(T), "x");
            var bindings = includeFields
                .Select(field => typeof(T).GetProperty(field))
                .Where(property => property != null)
                .Select(property => Expression.Bind(
                    property!,
                    Expression.Property(parameter, property!)
                ));

            var memberInit = Expression.MemberInit(Expression.New(typeof(T)), bindings);
            var lambda = Expression.Lambda<Func<T, T>>(memberInit, parameter);

            return query.Select(lambda);
        }
    
        public static IQueryable<T> IncludeDeleted<T>(this IQueryable<T> query, bool includeDeleted = true)
            where T : class, ISoftDelete
        {
            return includeDeleted ?
                query.IgnoreQueryFilters() :
                query;
        }

        public static IQueryable<T> OnlyDeleted<T>(this IQueryable<T> query)
            where T : class, ISoftDelete
        {
            return query.IgnoreQueryFilters()
                .Where(e => e.IsDeleted);
        }

        public static async Task RestoreAsync<T>(this DbContext context, int id)
            where T : BaseEntity
        {
            var entity = await context.Set<T>()
                .IgnoreQueryFilters()
                .FirstOrDefaultAsync(e => e.Id == id);

            if (entity != null && entity is ISoftDelete softDelete)
            {
                softDelete.IsDeleted = false;
                softDelete.DeletedAt = null;
                softDelete.DeletedBy = null;
                await context.SaveChangesAsync();
            }
        }

        public static async Task<int> BulkSoftDeleteAsync<T>(this DbContext context,
            Expression<Func<T, bool>> predicate) where T : class, ISoftDelete
        {
            var entities = await context.Set<T>()
                .Where(predicate)
                .ToListAsync();

            foreach (var entity in entities)
            {
                entity.IsDeleted = true;
                entity.DeletedAt = DateTime.UtcNow;
            }

            return await context.SaveChangesAsync();
        }

        public static async Task<int> HardDeleteAsync<T>(this DbContext context,
            Expression<Func<T, bool>> predicate) where T : class
        {
            var entities = await context.Set<T>()
                .IgnoreQueryFilters()
                .Where(predicate)
                .ToListAsync();

            context.Set<T>().RemoveRange(entities);
            return await context.SaveChangesAsync();
        }
    }

    public static class QueryBuilderExtensions
    {
        public static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition,
            Expression<Func<T, bool>> predicate)
        {
            return condition ? query.Where(predicate) : query;
        }

        public static IQueryable<T> Search<T>(this IQueryable<T> query, string keyword,
            params Expression<Func<T, string>>[] properties)
        {
            if (string.IsNullOrEmpty(keyword))
                return query;

            var parameter = Expression.Parameter(typeof(T), "x");
            var keywordExpr = Expression.Constant(keyword.ToLower());

            Expression? body = null;
            foreach (var property in properties)
            {
                var propertyAccess = Expression.Invoke(property, parameter);
                var toLower = Expression.Call(propertyAccess, "ToLower", Type.EmptyTypes);
                var contains = Expression.Call(toLower, "Contains", Type.EmptyTypes, keywordExpr);

                body = body == null ? contains : Expression.OrElse(body, contains);
            }

            if (body != null)
            {
                var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
                return query.Where(lambda);
            }
            return query;
        }

        public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query,
            Expression<Func<T, TValue>> property, IEnumerable<TValue> values)
        {
            if (values == null || !values.Any())
                return query;

            var parameter = property.Parameters[0];
            var propertyAccess = property.Body;
            var valuesList = values.ToList();

            var containsMethod = typeof(List<TValue>).GetMethod("Contains", new[] { typeof(TValue) });
            if (containsMethod == null)
                return query;
            var containsCall = Expression.Call(Expression.Constant(valuesList), containsMethod, propertyAccess);

            var lambda = Expression.Lambda<Func<T, bool>>(containsCall, parameter);
            return query.Where(lambda);
        }
    }

    public static class PagedResultExtensions
    {
        public static async Task<PagedResult<T>> ToPagedResultAsync<T>(
            this IQueryable<T> query, int pageNumber, int pageSize)
        {
            var totalCount = await query.CountAsync();
            var items = await query
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();

            return new PagedResult<T>
            {
                Items = items,
                TotalCount = totalCount,
                PageNumber = pageNumber,
                PageSize = pageSize,
                TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
            };
        }
    }

    public class PagedResult<T>
    {
        public List<T> Items { get; set; } = [];
        public int TotalCount { get; set; }
        public int PageNumber { get; set; }
        public int PageSize { get; set; }
        public int TotalPages { get; set; }
        public bool HasPreviousPage => PageNumber > 1;
        public bool HasNextPage => PageNumber < TotalPages;
    }

    public class PagedCriteria
    {
        public int Page { get; set; } = 1;
        public int PageSize { get; set; } = 20;
    }

    public interface ISoftDelete
    {
        bool IsDeleted { get; set; }
        DateTime? DeletedAt { get; set; }
        string? DeletedBy { get; set; }
    }

    public class ImportResult<T>
    {
        public List<T> SuccessItems { get; set; } = new();
        public List<ImportError<T>> Errors { get; set; } = new();
        public int TotalCount => SuccessItems.Count + Errors.Count;
    }

    public class ImportError<T>
    {
        public T? Item { get; set; }
        public int RowNumber { get; set; }
        public string? ErrorMessage { get; set; }
    }

    public class ValidationResult
    {
        public bool IsValid { get; set; }
        public string? ErrorMessage { get; set; }

        public static ValidationResult Success() => new() { IsValid = true };

        public static ValidationResult Error(string message) => new() { IsValid = false, ErrorMessage = message };
    }
}